﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework;

namespace Brain
{
    public class TerrainEditorHelper : IDisposable, IDrawable
    {
        public RenderTarget2D DecalTarget;

        public Effect DepthEffect;
        public Effect TerrainEffect;

        public TerrainWorldObject Terrain;

        public bool ShouldDraw { get; set; }

        public float PickerSize = 10;

        public Texture2D Painter;

        public int TrianglesPerFrame = 30000;

        private Vector3 Position;

        public ProceduralChecker CheckerTexture;
        
        public TerrainEditorHelper(TerrainWorldObject terrain)
        {
            ShouldDraw = true;

            this.Terrain = terrain;

            Painter = Engine.Instance.ContentManager.Load<Texture2D>("painter");
            TerrainEffect = Engine.Instance.ContentManager.Load<Effect>("TerrainEditorEffect");
            DepthEffect = Engine.Instance.ContentManager.Load<Effect>("DepthEffect");

            (Terrain.Model as BrainModel).Meshes[0].Effect = TerrainEffect;

            DecalTarget = new RenderTarget2D(Engine.Instance.GraphicsDevice, 128, 128);
            CheckerTexture = new ProceduralChecker(512, 12);

            TerrainEffect.Parameters["DiffuseTexture"].SetValue(CheckerTexture);
        }

        public void Dispose()
        {
            CheckerTexture.Dispose();
            TerrainEffect.Dispose();
            DepthEffect.Dispose();
            Painter.Dispose();
            if (!DecalTarget.IsDisposed)
            {
                DecalTarget.Dispose();
            }
        }

        int trianglesTested;
        public virtual void Update(int X, int Y, bool clicking, float Strength)
        {
            IRenderManager3D render = Engine.Instance.RenderManager3D;
            ICameraManager manager = Engine.Instance.CameraManager;
            if (render == null || manager == null)
            {
                return;
            }

            ICamera3D Camera = manager.Camera3D;
            GraphicsDevice device = Engine.Instance.GraphicsDevice;

            Matrix View = Camera.View;
            Matrix Projection = Camera.Projection;
            Viewport Viewport = device.Viewport;

            Vector2 mousePos = new Vector2(X, Y);

            if (Terrain.Triangles == null)
            {
                Terrain.MakeTriangles();
            }

            #region CPU Picking

            Vector3 near = Viewport.Unproject(new Vector3(mousePos, 0),
                Projection, View, Matrix.Identity);
            Vector3 far = Viewport.Unproject(new Vector3(mousePos, 1),
                Projection, View, Matrix.Identity);
            Vector3 direction = Vector3Util.Direction(near, far);
            Ray mouseRay = new Ray(Camera.Position, direction);

            bool knock = false;
            for (int i = trianglesTested; i < trianglesTested + TrianglesPerFrame; i++)
            {
                if (i >= Terrain.Triangles.Length)
                {
                    knock = true;
                    trianglesTested = 0;
                    break;
                }
                Triangle t = Terrain.Triangles[i];

                Vector3 ve1 = t.Vertex1;
                Vector3 ve2 = t.Vertex2;
                Vector3 ve3 = t.Vertex3;

                float? result;
                IntersectionUtil.RayIntersectsTriangle(ref mouseRay, ref ve1, ref ve2, ref ve3, out result);

                if (result != null)
                {
                    Position = TriangleUtil.Center(ve1, ve2, ve3);
                    break;
                }
            }
            if (!knock)
            {
                trianglesTested += TrianglesPerFrame;
            }

            #endregion

            if (clicking)
            {
                BoundingSphere ModifySphere = new BoundingSphere(Position, this.PickerSize);

                float offset = Terrain.HeightMap.OffsetBetweenHeights;
                int VertexCount = Terrain.HeightMap.Width * Terrain.HeightMap.Height;
                if (ModifySphere != null)
                {
                   for (int x = 0; x < Terrain.HeightMap.Width; x++)
                    {
                        for (int z = 0; z < Terrain.HeightMap.Height; z++)
                        {
                            float height = Terrain.HeightMap.Heights[x, z];
                            if (Terrain.HeightMap.Intersects(ModifySphere, height, x, z))
                            {
                                Terrain.HeightMap.Heights[x, z] = height + Strength;
                                int index = x + z * Terrain.HeightMap.Width;
                                render.Editor_UpdateTerrainVertex(
                                    Terrain, index,
                                    new Vector3(x * offset, height, -z * offset), new Vector2((float)x / 30.0f, (float)z / 30.0f));
                            }
                        }
                    }
                    for (int i = 0; i < Terrain.Triangles.Length; i++)
                    {
                        Triangle tri = Terrain.Triangles[i];

                        int index1 = Terrain.Indices[i];
                        int index2 = Terrain.Indices[i + 1];
                        int index3 = Terrain.Indices[i + 2];

                        Vector3 v1 = render.GetTerrainVertex(Terrain, index1);
                        Vector3 v2 = render.GetTerrainVertex(Terrain, index2);
                        Vector3 v3 = render.GetTerrainVertex(Terrain, index3);

                        Triangle triangle = new Triangle();
                        triangle.Vertex1 = v1;
                        triangle.Vertex2 = v2;
                        triangle.Vertex3 = v3;

                        triangle.Index1 = index1;
                        triangle.Index2 = index2;
                        triangle.Index3 = index3;

                        Terrain.Triangles[i / 3] = triangle;
                    }
                }
                Terrain.MakeTriangles();


                BrainModelMesh mesh = (Terrain.Model as BrainModel).Meshes[0];
                Engine.Instance.RenderManager3D.UpdateTerrainVertexBuffer(Terrain, mesh.VertexBuffer);
            }
        }

        public virtual void Draw()
        {
            IRenderManager3D render = Engine.Instance.RenderManager3D;
            ICameraManager manager = Engine.Instance.CameraManager;
            if (render == null || manager == null)
            {
                return;
            }
            
            ICamera3D Camera = manager.Camera3D;

            GraphicsDevice device = Engine.Instance.GraphicsDevice;

            Vector3 DecalPosition = Position + new Vector3(0, PickerSize, 0);
            Vector3 decalTarget = DecalPosition - new Vector3(0, 1, 0);
            Matrix DecalView = Matrix.CreateLookAt(DecalPosition, decalTarget, Vector3.Right);
            Matrix DecalProjection = Matrix.CreatePerspectiveFieldOfView(1, 1, 1, 1000);

            #region Rendering Decal Depth
            //Render the Depth to the Decal Target
            device.SetRenderTarget(this.DecalTarget);

            // Set the Depth Effect Parameters
            DepthEffect.Parameters["View"].SetValue(DecalView);
            DepthEffect.Parameters["Projection"].SetValue(DecalProjection);
            DepthEffect.Parameters["World"].SetValue(Matrix.Identity);
            DepthEffect.Parameters["MaxDepth"].SetValue(Camera.FarPlane);

            foreach (BrainModelMesh mesh in (Terrain.Model as BrainModel).Meshes)
            {
                if (mesh.Effect == null)
                    continue;

                device.Indices = mesh.IndexBuffer;
                device.SetVertexBuffer(mesh.VertexBuffer);

                int count = DepthEffect.CurrentTechnique.Passes.Count;

                for (int p = 0; p < count; p++)
                {
                    DepthEffect.CurrentTechnique.Passes[p].Apply();

                    device.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0,
                        mesh.NumVertices, mesh.VertexOffset, mesh.PrimitiveCount);
                }
            }

            device.SetRenderTarget(null);
            #endregion

            TerrainEffect.Parameters["LightPosition"].SetValue(new Vector3(0, 1000, 0));
            TerrainEffect.Parameters["LightColor"].SetValue(new Vector3(1, 1, 1));
            TerrainEffect.Parameters["LightPower"].SetValue(1);
            TerrainEffect.Parameters["LightAmbient"].SetValue(0.2f);
            TerrainEffect.Parameters["MaxDepth"].SetValue(Camera.FarPlane);

            TerrainEffect.Parameters["View"].SetValue(Camera.View);
            TerrainEffect.Parameters["Projection"].SetValue(Camera.Projection);
            TerrainEffect.Parameters["World"].SetValue(Terrain.WorldMatrix);
            TerrainEffect.Parameters["DecalViewProjection"].SetValue(DecalView * DecalProjection);
            TerrainEffect.Parameters["DecalMap"].SetValue(DecalTarget);
            TerrainEffect.Parameters["DecalTexture"].SetValue(Painter);
        }
    }
}
